
#include "qinputhooks.h"
#include <QDebug>
#include <QX11Info>
#include <QEvent>
#include <QThreadStorage>
#include <QMetaMethod>

#include "qinputhooks_x11.h"


void QInputHooks::initNativeHook()
{
    qRegisterMetaType<KeyEvent>();

    KeyHookerThread *hker;
    hker = new KeyHookerThread();
    this->nativeCtx = hker;
    hker->start();
}

void QInputHooks::activateNativeKeyEvent( void *nev )
{
    QMetaMethod signal = QMetaMethod::fromSignal(&QInputHooks::KeyDown);
    // signal.invoke(this, Qt::QueuedConnection);
    QKeyEvent e(QEvent::Type(0), 0, Qt::NoModifier);
    KeyEvent e_ = this->nativeKeyEvent(nev);
    // qDebug()<<"passwin:"<<uint(e_.wid);
    emit this->KeyDown(e_);
    KeyEventX ex_ = keyEventTox(e_);
    emit this->KeyDown2(ex_);
    // emit this->KeyDown(QEvent::Type(0), 0, Qt::NoModifier);
}

#include <X11/XKBlib.h>
KeySym KeyCodeToKeySym(Display * display, KeyCode keycode, unsigned int event_mask) {
    KeySym keysym = NoSymbol;

    //Get the map
    XkbDescPtr keyboard_map = XkbGetMap(display, XkbAllClientInfoMask, XkbUseCoreKbd);
    if (keyboard_map) {
        //What is diff between XkbKeyGroupInfo and XkbKeyNumGroups?
        unsigned char info = XkbKeyGroupInfo(keyboard_map, keycode);
        unsigned int num_groups = XkbKeyNumGroups(keyboard_map, keycode);

        //Get the group
        unsigned int group = 0x00;
        switch (XkbOutOfRangeGroupAction(info)) {
            case XkbRedirectIntoRange:
                /* If the RedirectIntoRange flag is set, the four least significant
                 * bits of the groups wrap control specify the index of a group to
                 * which all illegal groups correspond. If the specified group is
                 * also out of range, all illegal groups map to Group1.
                 */
                group = XkbOutOfRangeGroupInfo(info);
                if (group >= num_groups) {
                    group = 0;
                }
            break;

            case XkbClampIntoRange:
                /* If the ClampIntoRange flag is set, out-of-range groups correspond
                 * to the nearest legal group. Effective groups larger than the
                 * highest supported group are mapped to the highest supported group;
                 * effective groups less than Group1 are mapped to Group1 . For
                 * example, a key with two groups of symbols uses Group2 type and
                 * symbols if the global effective group is either Group3 or Group4.
                 */
                group = num_groups - 1;
            break;

            case XkbWrapIntoRange:
                /* If neither flag is set, group is wrapped into range using integer
                 * modulus. For example, a key with two groups of symbols for which
                 * groups wrap uses Group1 symbols if the global effective group is
                 * Group3 or Group2 symbols if the global effective group is Group4.
                 */
            default:
                if (num_groups != 0) {
                    group %= num_groups;
                }
            break;
        }

        XkbKeyTypePtr key_type = XkbKeyKeyType(keyboard_map, keycode, group);
        unsigned int active_mods = event_mask & key_type->mods.mask;

        int i, level = 0;
        for (i = 0; i < key_type->map_count; i++) {
            if (key_type->map[i].active && key_type->map[i].mods.mask == active_mods) {
                level = key_type->map[i].level;
            }
        }

        keysym = XkbKeySymEntry(keyboard_map, keycode, level, group);
        XkbFreeClientMap(keyboard_map, XkbAllClientInfoMask, true);
    }

    return keysym;
}
/*
int main(int argc, const char * argv[]) {
    Display * display;

    //Try to attach to the default X11 display.
    display = XOpenDisplay(NULL);
    if(display == NULL) {
        printf("Error: Could not open display!\n");
        return EXIT_FAILURE;
    }

    KeyCode keycode = 56; // b
    unsigned int event_mask = ShiftMask | LockMask;
    KeySym keysym = KeyCodeToKeySym(display, keycode, event_mask);

    printf("KeySym: %s\n", XKeysymToString(keysym));

    //Close the connection to the selected X11 display.
    XCloseDisplay(display);

    return EXIT_SUCCESS;
}
*/

#include "qbsdkeyboard_defaultmap.h"
static quint32 nativeKeysmToQtKey(quint16 ks)
{
    for (auto &m: keymapDefault) {
        if (m.unicode == ks) {
            return m.qtcode;
        }
    }
    return 0;
}

#include <X11/Xutil.h>
#include <X11/Xatom.h>
KeyEvent QInputHooks::nativeKeyEvent( void *ev )
{
    XEvent *nev = (XEvent*)ev;
    QKeyEvent qev(QEvent::Type(0), 0, Qt::NoModifier);

    int keyty = nev->type;
    int keycode = nev->xkey.keycode;
    QEvent::Type qkeyty;
    Qt::KeyboardModifiers qmods;
    QString qkeytxt;

    if (qkeyty == KeyPress) {
        qkeyty = QEvent::Type(6);
    }
    if (nev->xkey.state & ShiftMask) {
        qmods |= Qt::ShiftModifier;
    }
    if (nev->xkey.state & LockMask) {
        // qmod |= Qt::LockModifier;
    }
    if (nev->xkey.state & ControlMask) {
        qmods |= Qt::ControlModifier;
    }
    if (nev->xkey.state & Mod1Mask) {
        qmods |= Qt::AltModifier;
    }
    if (nev->xkey.state & Mod4Mask) {
        qmods |= Qt::MetaModifier;
    }

    // KeySym ks = XKeycodeToKeysym(QX11Info::display(), keycode, 0);
    KeySym ks = KeyCodeToKeySym(QX11Info::display(), keycode, nev->xkey.state);
    // ks = 0x66;
    char *kt = XKeysymToString(ks);
    qkeytxt = QString(kt);
    int qkeycode = nativeKeysmToQtKey(ks);
    // qDebug()<<(qkeycode == Qt::Key_F)<<int(qkeycode)<<int(Qt::Key_F);

    quint32 nativeScanCode = keycode;
    quint32 nativeVirtualKey = ks;
    quint32 nativeModifiers = nev->xkey.state;
    qev = QKeyEvent(qkeyty, qkeycode, qmods,
                    nativeScanCode, nativeVirtualKey, nativeModifiers,
                    qkeytxt);
    // qDebug()<<keyty<<keycode<<nev->xkey.type<<nev->xkey.state;
    // qDebug()<<qev.modifiers()<<qev.text()<<Qt::Key(qkeycode)<<qkeytxt<<int(Qt::Key_F);

    auto re_ =  KeyEvent(qev);
    re_.wid = nev->xkey.window;
    re_.wids = QString("%1").arg(nev->xkey.window);
    re_.time = nev->xkey.time;
    int revert_to_return = 0;
    Window fwid = 0;
    XGetInputFocus(QX11Info::display(), &fwid, &revert_to_return);
    re_.fwid = fwid;
    // qDebug()<<re_.fwid<<revert_to_return;
    XTextProperty txtprop;
    auto tpret = XGetTextProperty(QX11Info::display(), fwid, &txtprop, XA_WM_NAME);
    // qDebug()<<tpret<<QString((char*)txtprop.value);
    re_.wtitle = QString((char*)txtprop.value);

    /* qDebug()<<"window:"<<uint(nev->xkey.window)
            <<"subwin:"<<uint(nev->xkey.subwindow)
            <<"rootwin:"<<uint(nev->xkey.root)
            <<"wids:"<<re_.wids
            <<"passwin:"<<uint(re_.wid);*/

    return re_;
}

void KeyHookerThread::run() {
    // qDebug()<<"+++fffffffffff";
    this->main_cap();
}

///
#include <X11/Xlib.h>
#include <xcb/xcb.h>
#include <stdlib.h>
#include <iostream>

// using namespace std;

void* TaskCode(void* parg)
{
    int keycode = *((int*)parg);
    std::cout<< "\n\n" << keycode << "\n\n";
    // if(keycode == XKeysymToKeycode(XOpenDisplay(0),'a'))
    //  system("espeak -v en " "\"a\"");
    delete (int*)parg;
    return 0;
}

void Action(int keycode)
{
    pthread_t thread;
    pthread_attr_t  attrs;
    pthread_attr_init(&attrs);
    pthread_attr_setdetachstate(&attrs,PTHREAD_CREATE_DETACHED);
    pthread_attr_setstacksize(&attrs, 1000);
    int* pthread_arg = new int;
    *pthread_arg = keycode;
    pthread_create(&thread,&attrs, TaskCode, (void*) pthread_arg);
}

int MyX11ErrorHandler(Display *, XErrorEvent *error_event)
{
    std::cout << "\n\n" "An X11-Functions error occured. Probably the focused window was closed.""\n"
           "This error will be ignored." "\n";
    std::cout<< "error_code: " << (unsigned)error_event -> error_code << "\n";
    std::cout<< "minor_code: " << (unsigned)error_event -> minor_code << "\n";
    std::cout<< "request_code: " << (unsigned)error_event -> request_code << "\n";
    std::cout<< "resourceid: " << error_event -> resourceid << "\n";
    std::cout<< "serial; " << error_event -> serial << "\n";
    std::cout<< "type: " << error_event -> type << "\n\n";
   return 0;
}

int KeyHookerThread::main_cap()
{
    Display* display = XOpenDisplay(0);
    Window root = DefaultRootWindow(display);
    Window current_focus_window;
    int revert;

    XSetErrorHandler(MyX11ErrorHandler);

    XGetInputFocus(display, &current_focus_window, &revert);
    XSelectInput(display,current_focus_window,KeyPressMask | KeyReleaseMask | FocusChangeMask);

    while(true)
    {
        XEvent event;
        XNextEvent(display, &event);
        switch (event.type)
        {
            case FocusOut:
                if(current_focus_window != root)
                    XSelectInput(display, current_focus_window, 0);
                XGetInputFocus(display, &current_focus_window, &revert);
                if(current_focus_window == PointerRoot)
                    current_focus_window = root;
                XSelectInput(display, current_focus_window, KeyPressMask|KeyReleaseMask|FocusChangeMask);
                break;

            case KeyPress:
                // Action(event.xkey.keycode);
                // qDebug()<<"hehhehe";
                QInputHooks::instance()->activateNativeKeyEvent(&event);
                break;
        case KeyRelease:
            break;
        }
    }
    qDebug()<<"returned!!!";
}
